package org.kisekiproject.evaluator {
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	import flashx.textLayout.elements.BreakElement;
	
	import org.kisekiproject.evaluator.errors.ExpressionError;
	import org.kisekiproject.evaluator.nodes.BinaryOperatorNode;
	import org.kisekiproject.evaluator.nodes.ConstantNode;
	import org.kisekiproject.evaluator.nodes.FunctionNode;
	import org.kisekiproject.evaluator.nodes.ISyntaxNode;
	import org.kisekiproject.evaluator.nodes.IfNode;
	import org.kisekiproject.evaluator.nodes.IndirectionNode;
	import org.kisekiproject.evaluator.nodes.MethodNode;
	import org.kisekiproject.evaluator.nodes.TokenNode;
	import org.kisekiproject.evaluator.nodes.UnaryOperatorNode;
	import org.kisekiproject.evaluator.nodes.VariableNode;

	/**
	 * @author mikael
	 */
	public class Expression extends EventDispatcher implements IExpression {

		private var _variables:IVariableNamespace;
		private var _node : ISyntaxNode;
		private var _expression : String;
		//private var _valueFetched:Boolean;

		/**
		 * Constructor.
		 */
		public function Expression(expression:String, variables:IVariableNamespace) {
			_variables=variables;
			_expression=expression;

			_node=parse(expression);
			_node.addEventListener(Event.CHANGE,onNodeChange);
		}

		/**
		 * Node change, propagate.
		 */
		private function onNodeChange(event:Event):void {
			//if (_valueFetched)
				dispatchEvent(new Event(Event.CHANGE));
		}

		/**
		 * Get value.
		 */
		public function get value():Object {
			//_valueFetched=true;
			return _node.value;
		}

		/**
		 * Parse the expression and build AST.
		 * Implements http://en.wikipedia.org/wiki/Shunting-yard_algorithm
		 */
		private function parse(expression:String):ISyntaxNode {
			var tokenizer:Tokenizer=new Tokenizer();
			var tokens:Array=tokenizer.tokenize(expression);

			tokens=rpn(tokens);

			trace("--- rpn ---");
			for each (var t:Token in tokens)
				trace("rpn: "+t);

			return ast(tokens);
		}

		/**
		 * Build ast.
		 * The tokens are aassumed to be rpn.
		 */
		private function ast(tokens:Array):ISyntaxNode {
			var stack:Array=new Array();
			var args:Array;
			var i:int;

			for each (var token:Token in tokens) {
				switch (token.type) {
					case Token.NUMBER:
					case Token.STRING:
						stack.push(new ConstantNode(token.value,token.index));
						break;

					case Token.FLOW:
						switch (token.string) {
							case "if":
								trace("stacklen: "+stack.length);
								if (stack.length<2)
									throw new ExpressionError("Parse error",_expression,token.index);

								var elseNode:ISyntaxNode=popIfElse(stack);
								var trueNode:ISyntaxNode=stack.pop();

								if (!stack.length)
									throw new ExpressionError("Parse error",_expression,token.index);

								var ifNode:ISyntaxNode=stack.pop();
								stack.push(new IfNode(ifNode,trueNode,elseNode,token.index));
								break;

							case "else":
								stack.push(new TokenNode(token,stack.pop(),_expression));
								break;
						}
						break;

					case Token.SYMBOL:
						stack.push(new VariableNode(_variables,token.string,token.index));
						break;

					case Token.BINARY_OPERATOR:
						if (stack.length<2)
							throw new ExpressionError("Parse error",_expression,token.index);

						if (!BinaryOperatorNode.isBinaryOperator(token.string))
							throw new ExpressionError("Parse error",_expression,token.index);

						var right:ISyntaxNode=stack.pop();
						var left:ISyntaxNode=stack.pop();
						stack.push(new BinaryOperatorNode(token.string,left,right,token.index));
						break;

					case Token.UNARY_OPERATOR:
						if (stack.length<1)
							throw new ExpressionError("Parse error",_expression,token.index);

						stack.push(new UnaryOperatorNode(token.string,stack.pop(),token.index));
						break;

					case Token.INDIRECTION:
						stack.push(new IndirectionNode(stack.pop(),token.string,token.index));
						break;

					case Token.FUNCTION_CALL:
						if (stack.length<token.argumentCount)
							throw new ExpressionError("Parse error",_expression,token.index);

						args=new Array();
						for (i=0; i<token.argumentCount; i++)
							args.push(stack.pop());

						stack.push(new FunctionNode(_variables,token.string,args,token.index));
						break;

					case Token.METHOD_CALL:
						if (stack.length<token.argumentCount+1)
							throw new ExpressionError("Parse error",_expression,token.index);

						args=new Array();
						for (i=0; i<token.argumentCount; i++)
							args.push(stack.pop());

						var obj:ISyntaxNode=stack.pop();
						stack.push(new MethodNode(_variables,obj,token.string,args,token.index));
						break;

					default:
						throw new ExpressionError("Parse error",_expression,token.index);
						break;
				}
			}

			if (stack.length!=1) {
				for each (var n:ISyntaxNode in stack)
					trace("left on stack: "+n);

				throw new ExpressionError("Parse error",_expression,0);
			}

			return stack[0];
		}

		/**
		 * Pop the top of stack if it is an else node.
		 */
		private function popIfElse(stack:Array):ISyntaxNode {
			if (stack.length==0)
				return null;

			var node:ISyntaxNode=stack[stack.length-1];
			if (node is TokenNode && TokenNode(node).token.type==Token.FLOW && TokenNode(node).token.string=="else") {
				//trace("popping else...");
				stack.pop();
				return TokenNode(node).node;
			}

			return null;
		}
		
		/**
		 * Convert to rpn.
		 * TODO: Handle paranthesis.
		 */
		private function rpn(tokens:Array):Array {
			var queue:Array=new Array();
			var stack:Array=new Array();
			var unaryMinus:Boolean=true;
			var callType:String=null;
			var tmp:Token;
			var last:Token=null;
			while (tokens.length) {
				var t:Token=tokens.shift();

				//trace("tokenizing: "+t+" unaryMinus: "+unaryMinus);

				switch (t.type) {
					case Token.NUMBER:
					case Token.STRING:
						queue.push(t);
						unaryMinus=false;
						callType=null;
						break;

					case Token.SYMBOL:
						queue.push(t);
						unaryMinus=false;
						callType=Token.FUNCTION_CALL;
						break;

					case Token.FLOW:
						stack.push(t);
						unaryMinus=true;
						callType=null;
						break;

					case Token.BINARY_OPERATOR:
						callType=null;

						if (t.string==".") {
							var prop:Token=tokens.shift();

							if (!prop)
								throw new ExpressionError("Parse error",_expression,t.index);

							if (prop.type!=Token.SYMBOL)
								throw new ExpressionError("Parse error",_expression,prop.index);

							queue.push(new Token(Token.INDIRECTION,prop.string,t.index));
							callType=Token.METHOD_CALL;
						}

						else if (t.string=="-" && unaryMinus) {
							stack.push(new Token(Token.UNARY_OPERATOR,t.string,t.index));
						}

						else if (BinaryOperatorNode.isBinaryOperator(t.string)) {
							while (compareTopOfStackWithOperator(stack,t))
								queue.push(stack.pop());

							stack.push(t);
						}

						else
							throw new ExpressionError("Parse error",_expression,t.index);

						unaryMinus=true;
						break;

					case Token.LEFT_PARENTHESES:
						tmp=null;
						switch (callType) {
							case Token.FUNCTION_CALL:
								tmp=queue.pop();
								tmp=new Token(Token.FUNCTION_CALL,tmp.value,tmp.index);
								tmp.argumentCount=1;
								//stack.push(new Token(Token.FUNCTION_CALL,tmp.value,tmp.index));
								break;

							case Token.METHOD_CALL:
								tmp=queue.pop();
								tmp=new Token(Token.METHOD_CALL,tmp.value,tmp.index);
								tmp.argumentCount=1;
								//stack.push(new Token(Token.METHOD_CALL,tmp.value,tmp.index));
								break;
						}

						stack.push(t);

						if (tmp) {
							t.callToken=tmp;
							stack.push(tmp);
						}

						unaryMinus=true;
						callType=null;
						break;
							
					case Token.RIGHT_PARENTHESES:
						while (stack.length>0 && Token(stack[stack.length-1]).type!=Token.LEFT_PARENTHESES) {
							tmp=stack.pop();
							queue.push(tmp);
						}

						if (stack.length==0)
							throw new ExpressionError("Parse error",_expression,t.index);

						var lpToken:Token=stack.pop();
						unaryMinus=false;
						callType=null;

						if (last.type==Token.LEFT_PARENTHESES && lpToken.callToken)
							lpToken.callToken.argumentCount=0

						break;

					default:
						throw new ExpressionError("Parse error",_expression,t.index);
				}

				last=t;
			}

			while (stack.length)
				queue.push(stack.pop());

			return queue;			
		}

		/**
		 * Implements the test for:
		 * If the token is an operator, o1, then:
		 * while there is an operator token, o2, at the top of the stack, and
		 * either o1 is left-associative and its precedence is less than or equal to that of o2,
		 * or o1 is right-associative and its precedence is less than that of o2,
		 * pop o2 off the stack, onto the output queue;
		 * push o1 onto the stack.
		 */
		private static function compareTopOfStackWithOperator(stack:Array, t:Token):Boolean {
			if (stack.length<1)
				return false;

			var top:Token=stack[stack.length-1];

			return (
				top.type==Token.BINARY_OPERATOR && 
				BinaryOperatorNode.isBinaryOperator(top.string) &&
				BinaryOperatorNode.getOperatorPrecedence(t.string)<=
					BinaryOperatorNode.getOperatorPrecedence(top.string)
			);
		}
	}
}